`timescale 1ns / 1ps
/*-------------------------------------------------------------------------------------*\
FileName        : cbb_uart.v
Author          ：hpy
Email           ：yuan_hp@qq.com
Date            ：2023年04月03日
Description     ：
模块需要将要发送的数据写入FIFO，FIFO的深度可以自行在调用时进行配置，默认深度为16，
模块通过通过检测发送FIFO为非空，则启动1次UART数据发送，接收的UART数据也写入接收FIFO，用户判断
接收FIFO非空可以读取数据 ， 连续读取的语法如下

模块根据时钟频率和波特率自动计算参数

always @(posedge sys_clk) begin
	if( ~rst_n) begin 
		wfwe <= 1'b0 ;
		rfre <= 1'b0 ;
	end else begin
		wfwe <= 1'b0 ;
		rfre <= 1'b0 ;
		if((~rfempty) & (~rfre)) begin 
			wfwe <= 1'b1 ;
			rfre <= 1'b1;
			din <= dout ; 
		end 
	end
end 

1.cr0 控制寄存器
    cr0[0]:UART使能信号，高有效，低电平时可以降低功耗    
    cr0[3:1]:校验位设置 0，无校验 ，1奇校验 2偶校验 3校验位始终为1 4校验位始终为0
    cr0[5:4]:停止位设置 0 1bit停止位  1 1.5bit停止位  2 2bit停止位

2. sr0 状态寄存器
    sr0[0]: rfempty;
    sr0[1]: wffull;
    sr0[2]: NC;  
    sr0[3]: 读忙;  
    sr0[4]: 发送忙;  //模块是否忙  1:忙 0:空闲
    sr0[5]: rfwe ;  //可作为读数据中断

// 例化模板
cbb_uart #(
    .CLK_FREQ(12000000) , 
	.BSP(115200) ,
    .dw (8 ) ,//fifo数据宽度
    .de (16)   //fifo数据深度，verilog根据设置值，自动向上修正为2^n值
)puartr_u1 (
    .clk( clk ),
    .rst_n( rst_n ),
    .cr0( ),//[7:0]  控制寄存器0 
    .sr0( ),//[7:0]  状态寄存器

    .din ( ),  //写入发送fifo的数据
    .dout( ),  //UART读到的数据
    .wfwe   ( ) ,
    .rfre   ( ) ,
    .wffull ( ) ,
    .rfempty( ) ,
    .tx_done() , //发送1字节完毕产生一个系统时钟的高电平
    .rx_done() , //接收1字节完毕产生一个系统时钟的高电平
    // uart 接口
    .tx( ) , //output
    .rx( )   //input 
);
\*----------------------------------------------------------------------------------------*/
module cbb_uart #(
    parameter CLK_FREQ = 12000000, //输入时钟
    parameter BSP = 115200 , // 波特率
    parameter dw =8  ,//fifo数据宽度
    parameter de =16   //fifo数据深度，verilog根据设置值，自动向上修正为2^n值
) (
    input               clk,
    input               rst_n,
    input   [7:0]       cr0 ,//控制寄存器0

    output  [7:0]       sr0 ,// 状态寄存器

    input   [7:0]       din,  //写入发送fifo的数据
    output  [7:0]       dout, //读到的数据
    input               wfwe,
    input               rfre,
    output              wffull,
    output              rfempty,
    output    reg       tx_done , //中断信号 ， 产生一个clk的高电平
    output    reg       rx_done ,
    // uart 接口 
    output reg          tx ,   
    input               rx

);
//------------根据时钟自动计算参数值 -----------------
function integer clog2(input integer i);
    for(clog2=0;i>0 ; clog2=clog2+1)
        i=i>>1; 
endfunction 

localparam  integer TX_CNT_MAX =   CLK_FREQ / BSP;

localparam  RX_CNT_F = CLK_FREQ / (1.00*BSP);
localparam  integer RX_CNT_I = CLK_FREQ / BSP;
localparam  integer RX_CNT_MAX =   CLK_FREQ / (BSP) ;//CLK_FREQ / ((RX_CNT_I+1)/RX_CNT_F*BSP);//CLK_FREQ / (1.01*BSP);


localparam TX_CNT_MAX_DW = clog2(TX_CNT_MAX-1) ; 
localparam RX_CNT_MAX_DW = clog2(RX_CNT_MAX-1) ; 
//---------------------- param ---------------------
localparam 
    TX_IDLE        =3'b000,
    TX_START       =3'b001,
    TX_SHIFT       =3'b011, 
    TX_PARITY      =3'b010, 
    TX_STOP        =3'b110;

localparam 
    RX_IDLE        =3'b000 , 
    RX_START       =3'b001 ,
    RX_SHIFT       =3'b011 ,
    RX_PARITY      =3'b010 ,
    RX_STOP        =3'b110 ;
//校验位设置值
localparam 
    NO_PARITY      = 3'h0  ,  //无校验
    ODD_PARITY     = 3'h1  ,  //奇校验
    EVEN_PARITY    = 3'h2  ,  //偶校验
    MARK_PARITY    = 3'h3  ,  //校验位始终为1
    ZERO_PARITY    = 3'h4  ;  //校验位始终为0 

//停止位设置值
localparam 
    STOP_1_0BIT      = 2'h0  , //1 位停止位
    STOP_1_5BIT      = 2'h1  , //1.5位停止位
    STOP_2_0BIT      = 2'h2  ; //2.0位停止位

//----------------------- reg ----------------------
reg [2:0] tx_state  , rx_state; //状态机
reg [ TX_CNT_MAX_DW - 1 :0] txclk ;
reg [ RX_CNT_MAX_DW - 1 :0] rxclk ;

//发送fifo 
 
reg wfre,rfwe ; 


//-----------------------wire ---------------------
wire enable  , start ; //iic使能控制，高有效
wire [2:0] parity ;  //校验位设置
wire [1:0] stopbit ; //停止位设置


// wire wfwe ,wffull , wfempty  ;
wire [7:0] wfdout ; 


assign enable   = cr0[0] ;   //使能IIC 控制器
assign parity   = cr0[3:1] ; //是否使能就校验位
assign stopbit  = cr0[5:4] ; //


assign sr0[0] = rfempty;
assign sr0[1] = wffull;
// assign sr0[2] = ;
assign sr0[3] = (rx_state==RX_IDLE)?1'b0:1'b1;  // 
assign sr0[4] = (tx_state==TX_IDLE)?1'b0:1'b1;  //模块是否忙  1:忙 0:空闲
assign sr0[5] = rfwe ; //可作为读数据中断

//时钟分频 计数器 
always @(posedge clk) begin
    if( enable & (|txclk & |tx_state) & (~tx_done) ) 
        txclk <= txclk - 1'b1 ; 
    else 
        txclk <= TX_CNT_MAX - 1'b1 ; 
end 


always @(posedge clk) begin
    if( enable & (|rxclk & |rx_state) & (~rx_done) ) 
        rxclk <= rxclk - 1'b1 ; 
    else 
        rxclk <= RX_CNT_MAX - 1'b1 ; 
end 


cbb_fifo #(
	.de(de), //数据深度，任意正整数，若不是2的幂，会向上自动取2的幂次值，如设置为3，那么实际深度为4
    .dw(dw)    //数据宽度，
)wffifo (
	.clk(clk),    //系统时候时钟
	.rst_n(rst_n), //复位信后 ，低有效
	.clr(~enable),    // fifo清空，高有效
	.din(din),    //输入的数据
	.we(wfwe),     //写使能 ， 高有效
	.re(wfre),     //读使能，若非空，先读数据然后使能一次，则数据出队
    .dout(wfdout), //读出的数据
    .full( wffull) ,    //fifo满则为1 
    .empty( wfempty)     //fifo空则为1
);

cbb_fifo #(
	.de(de), //数据深度，任意正整数，若不是2的幂，会向上自动取2的幂次值，如设置为3，那么实际深度为4
    .dw(dw)    //数据宽度，
)rffifo (
	.clk(clk),    //系统时候时钟
	.rst_n(rst_n), //复位信后 ，低有效
	.clr(~enable),    // fifo清空，高有效
	.din( rreg[7:0]),    //输入的数据
	.we(rfwe),     //写使能 ， 高有效
	.re(rfre),     //读使能，若非空，先读数据然后使能一次，则数据出队
    .dout( dout), //读出的数据
    .full( rffull) ,    //fifo满则为1 
    .empty( rfempty)     //fifo空则为1
);

reg [ 7:0] rreg ; 
reg [15:0] write_cnt_r , read_cnt_r ;  // 读或者写的字节数
reg [1:0] start_f ; // 打拍
reg [7:0] tx_shift ;
reg [2:0] tx_shift_cnt ;
reg tx_parity ; 
reg [2:0] tx_stopcnt ; 
//发送数据
always @(posedge clk ) begin
    if( ~enable | ~rst_n) begin
        tx_state    <= TX_IDLE ; 
        wfre        <= 1'b0;
        tx_shift_cnt<= 3'h0;
        tx_parity   <= 1'b1;
        tx_stopcnt  <= 3'h0;
        tx_done     <= 1'b0;
        tx_shift    <= 8'hff;
    end else begin 
        wfre <= 1'b0;
        tx_done    <= 1'b0;

        case(tx_state) 
            TX_IDLE: 
                begin
                    if(~wfempty) begin 
                        tx_state <= TX_START ; 
                        tx <= 1'b0 ; //
                        tx_shift_cnt <= 3'h7 ;
                    end else tx <= 1'b1 ;
                end
            TX_START :
                begin 
                    if(~|txclk)begin 
                        tx_shift <= { 1'b1 , wfdout[7:1]} ;                         
                        tx <= wfdout[0] ;
                        tx_shift_cnt <= 3'h7 ;
                        wfre <= 1'b1;
                        tx_state <= TX_SHIFT ;  
                        tx_parity <= ^wfdout;
                    end 
                end
            TX_SHIFT:
                begin 
                    if(~|txclk) begin 
                        if(~|tx_shift_cnt) begin 
                            if(~|parity) begin
                                tx_state <= TX_STOP ; 
                                tx    <= 1'b1 ;
                                tx_stopcnt <= stopbit + 1'b1 ;
                            end else begin
                                tx_state <= TX_PARITY ;
                                case(parity)
                                   ODD_PARITY :  tx <= ~tx_parity ; 
                                   EVEN_PARITY:  tx <= tx_parity ;
                                   MARK_PARITY:  tx <= 1'b1 ;
                                   ZERO_PARITY:  tx <= 1'b0 ;
                                   default:      tx <= ~tx_parity ;
                                endcase
                            end  
                        end else begin 
                            tx_state     <= TX_SHIFT ;
                            tx_shift_cnt <= tx_shift_cnt - 1'b1 ;
                            tx_shift     <= { 1'b1 , tx_shift[7:1]} ;                         
                            tx           <= tx_shift[0] ;
                        end 
                    end
                end 
            TX_PARITY : //奇偶校验 
                begin
                    if(~|txclk) begin
                        tx_state <= TX_STOP ; 
                        tx    <= 1'b1 ;
                        tx_stopcnt <= stopbit + 1'b1 ;
                    end 
                end
            TX_STOP: //停止位 
                begin
                    if( (~|txclk) | (txclk == (TX_CNT_MAX>>1) ) ) begin
                        if(~|tx_stopcnt) begin //一次发送结束
                            tx_done    <= 1'b1;   
                            tx <= 1'b1 ;
                            tx_state <= TX_IDLE ;
                        end else begin 
                            tx_stopcnt <= tx_stopcnt - 1'b1 ;
                        end 
                    end
                end
            default: tx_state <= TX_IDLE ; 
        endcase 
    end
end


// 接收数据
reg [7:0] rx_shift ; 
reg [2:0] rx_shift_cnt ;
reg [2:0] rx_stopcnt ;
always @(posedge clk) begin
    if(  ~enable | ~rst_n ) begin
        rfwe <= 1'b0 ;
        rreg <= 8'h0 ;
        rx_state <= RX_IDLE ;
        rx_shift <= 8'h0 ;
        rx_shift_cnt <= 3'h0 ;
        rx_done <= 1'b0;
    end else begin
        rfwe <= 1'b0 ;
        rx_done <= 1'b0;
 
        case( rx_state ) 
            RX_IDLE:
                begin
                    if( ~rx ) begin
                        rx_state <= RX_START ;
                    end
                end
            RX_START:
                begin
                    if(~|rxclk) begin 
                        rx_state <= RX_SHIFT ;
                        rx_shift_cnt <= 3'h7 ;
                    end
                end
            RX_SHIFT:
                begin
                    if(rxclk == RX_CNT_MAX>>1 ) begin
                        rx_shift <= {rx,rx_shift[7:1]} ;
                    end else if (~|rxclk) begin
                        if(~|rx_shift_cnt) begin
                            rreg <= rx_shift ; 
                            if ( ~|parity) begin
                                rx_state <= RX_STOP ;
                                rx_stopcnt <= stopbit + 1'b1 ;
                                rfwe <= 1'b1 ;
                            end else begin
                                rx_state <= RX_PARITY;
                            end 
                        end else rx_shift_cnt <= rx_shift_cnt - 1'b1 ;
                    end
                end 
            RX_PARITY : 
                begin
                    if(~|rxclk)begin
                        rx_state <= RX_STOP; 
                        rx_stopcnt <= stopbit + 1'b1 ;
                        rfwe <= 1'b1 ;
                    end
                end
            RX_STOP : 
                begin
					rx_state <= RX_IDLE ;
                    /*if ( ( ~|rxclk ) | (rxclk == RX_CNT_MAX>>1) ) begin
                        if(~|rx_stopcnt) begin 
                            rx_state <= RX_IDLE ; 
                            rx_done <= 1'b1;
                        end else rx_stopcnt <= rx_stopcnt - 1'b1 ;
                    end */
                end
            default: rx_state <= RX_IDLE ; 
        endcase 
    end
end 

endmodule
